package models;

import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.JoinTable;
import javax.persistence.JoinColumn;
import javax.persistence.Lob;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Query;
import javax.persistence.Transient;

import models.ServiceReoccurrenceData.Period;

import com.google.gson.Gson;

import play.data.validation.Required;
import play.db.jpa.JPA;
import play.db.jpa.JPABase;
import play.db.jpa.Model;
import play.modules.search.Field;
import play.modules.search.Indexed;
import service.CreditManager;
import service.NotificationManager;
import controllers.BadgeManager;
import controllers.Handshakes;
import controllers.Security;

@Entity
public abstract class Service extends Model implements Cloneable
{
	@Transient
	public float Relevancy;		//for BingoSearch use
	@Transient
	public Boolean IsOffer;		//for BingoSearch use

	@Required
	@Field
    public String title;
	
    @Temporal(TemporalType.TIMESTAMP)
    public Date startsAt;
	
    @Temporal(TemporalType.TIMESTAMP)
    public Date endsAt;
    
    /*
     * Seyfi : For social credit test
     *  it can be removed when startsAt and endsAt implemented
     */
    public double duration;
	
	@Required
    @Lob
    @Field
    public String description;
	
	@Required
	@ManyToMany(cascade=CascadeType.ALL)
    public List<Tag> tags;
	
	/**
	 * Parent service. A service is said to be parent
	 * if it represents a set of sessions and child if
	 * it represents a single session of a service. 
	 */
	@ManyToOne
	public Service parent;
	
	/**
	 * All sessions of a session. This list is created
	 * only if the service is a parent service.
	 */
	@OneToMany(mappedBy="parent")
	public List<Service> sessions = new ArrayList<Service>();
	
	/**
	 * Owner of the service.
	 */
	@ManyToOne
	public User owner;
	
	/**
	 * Number of participants, which makes a service
	 * applicable.
	 */
	public int minNumberOfParticipants = 1;
	
	/**
	 * Maximum number of attendees count.
	 */
	public int maxNumberOfParticipants = 1;
	
	/**
	 * Geolocation of the service; if it's not virtual.
	 */
	public String location;
	
	/**
	 * Range from the location
	 */
	public double rangeFromLocation=5;
	
	public boolean isVirtual;
	
	/**
	 * Requesters or offerers of the service. Users in
	 * this set may be waiting for the owner's approval
	 * or accepted/rejected by the owner.
	 */
	@OneToMany(mappedBy="service", cascade={CascadeType.ALL}, orphanRemoval=true)
	public Set<ServiceHasParticipants> participants = new HashSet<ServiceHasParticipants>();
			
	/**
	 * Status of the service.
	 * 
	 * @TODO verify service statuses.
	 */
	public enum Status {
        //WAITING, HANDSHAKED
		//WAITING_APPROVAL, ACCEPTED, REJECTED, STARTED, CANCELLED, DONE, UNKNOWN, IGNORED
		CREATED, CANCELLED, HANDSHAKED, COMPLETED, TRADED
		
    }
	
	public Status status;
	
	/**
	 * Data about the re-occurrence status of the service.
	 */
	@OneToOne
	public ServiceReoccurrenceData reoccurrenceData; 
	
	/**
	 * Given a user, add him/her to the session.
	 * 
	 * @param user	user that will be added to the session.
	 */
	public void addUser(User user)
	{
		ServiceHasParticipants relation = new ServiceHasParticipants(this, user);
		this.participants.add(relation);
		this.em().persist(relation);
	}
	
	/**
	 * Given a user of the current session, revokes him/her from
	 * the session. 
	 * 
	 * @param user 	user that will be revoked from the session.
	 */
	public void revokeUser(User user)
	{
		for (ServiceHasParticipants shp : user.appliedServices) {
			if (this.equals(shp.service)) {
				this.em().createQuery("delete from ServiceHasParticipants where user_id=" + user.id + " and service_id=" + shp.service.id).executeUpdate();
				
//				this.em().remove(shp);
//				this.em().flush();
				return;
			}
		}
	}
	
	public void approveUser(User user) {
		this.setStatusOfUserRelation(user, ServiceHasParticipants.Status.APPROVED);
	}
	
	public void rejectUser(User user) {
		this.setStatusOfUserRelation(user, ServiceHasParticipants.Status.REJECTED);
	}
	
	private void setStatusOfUserRelation(User user, ServiceHasParticipants.Status status) {
		for (ServiceHasParticipants rel : user.appliedServices) {
			if (this.equals(rel.service)) {
				rel.status = status;
				this.em().flush();
				return;
			}
		}
	}

	/**
	 * Returns the duration of the current session or the
	 * sum of all sessions' durations.
	 * 
	 * @return	duration of the session(s).
	 */
	public double getDuration() {
		double duration = 0.0;
		
		if (! this.isSession()) {
			for (Service session : this.sessions) {
				duration += session.getDuration();
			}
		} 
		
	    /*
	     * Seyfi : For social credit test
	     *  it can be removed when startsAt and endsAt implemented
	     */		
		else if (0.0 < this.duration)
		{
			duration = this.duration;
		}
		else if (null != this.startsAt && null != this.endsAt) {
			duration = (this.endsAt.getTime() - this.startsAt.getTime()) / 3600000;
		}
		
		return duration;
	}
	
	public String getType() {
		return this.getClass().getName().substring(7);
	}
	
	public double getSessionDuration() {
		return duration;
	}
	
	public Set<User> getParticipants() {
		Set<User> participants = new HashSet<User>();
		
		for (ServiceHasParticipants relation : this.participants) {
			participants.add(relation.user);
		}
		
		return participants;
	}
	
	public boolean isSession() {
		return (null != this.parent) || (0 == this.sessions.size());
	}
	
	public Service getParent() {
		return this.parent;
	}
	
	public CreditType getCredits() {
		return CreditManager.getService(this);
	}
	
	public List<Tag> getTags()
    {
        return this.tags;
    }
	
	public String getDescription()
    {
        return this.description;
    }
	
	public String getTitle()
    {
        return this.title;
    }
	
	/**
	 * Method used to create sessions for a parent service.
	 * 
	 * @TODO build the algorithm and refine the method
	 * signature.
	 */
	protected abstract void createSessions();
	
	/**
	 * Method used to updates sessions for a parent service.
	 * Used to change information for all child services at
	 * once.
	 * 
	 * @TODO build the algorithm and refine the method
	 * signature.
	 */
	protected abstract void updateSessions();
	
	/**
	 * Method used to delete all sessions of a parent service.
	 * 
	 * @TODO for each session of the service, remove the child
	 * service and its relations.
	 */
	protected abstract void deleteSessions();
	
	public boolean equals(Object o) {
		return this.id == ((Service) o).id;
	}
	//03.06.2012 Seyfi: Set status according to the number of participants.
	public int getNumberOfApprovedParticipants()
	{
		int currentNumberOfParticipants = 0;
		Query numberOfParticipant = JPA.em().createQuery("select count(*) from " + ServiceHasParticipants.class.getName() + " where service_Id=" + this.id + " and status = 1 ");
		currentNumberOfParticipants = ((Long)numberOfParticipant.getSingleResult()).intValue();
		return currentNumberOfParticipants;
	}
	public void setStatusByNumberOfParticipants()
	{
		int currentNumberOfParticipants = this.getNumberOfApprovedParticipants();
		if (currentNumberOfParticipants >= this.minNumberOfParticipants)
		{
			this.status = Service.Status.HANDSHAKED;
			HandshakeCountIncrease();
		}
		else
		{
			this.status = Service.Status.CREATED;
		}
		this.save();
	}
//..Conflict
public void setStatusByTime()
	{
		int currentNumberOfParticipants = this.getNumberOfApprovedParticipants();
		if (currentNumberOfParticipants >= this.minNumberOfParticipants)
		{
			if (this.status != Service.Status.COMPLETED)
			{
				this.status = Service.Status.COMPLETED;
				this.sendNotificationForFeedback();
			}
		}
		else
		{
			this.status = Service.Status.CANCELLED;
		}
		this.save();
		Handshakes.feedback(this.id);
	}
public void sendNotificationForFeedback()
	{
		Query acceptedParticipant = JPA.em().createQuery("from " + ServiceHasParticipants.class.getName() + " where service_Id=" + this.id + " and status = 1 ");
		List<Object[]> applicationStatus = acceptedParticipant.getResultList();
		List<ServiceHasParticipants> acceptedParticipants = new ArrayList(applicationStatus);
    	String message;
    	String link;
    	message = this.title + " handshake is completed, please give us feedback";
    	link = "handshakes/feedback?id=" + this.id;
    	
		for (ServiceHasParticipants acceptedShp: acceptedParticipants)
		{
			User acceptedUser = acceptedShp.user;
			NotificationManager nManager = new NotificationManager();
			try {
				nManager.AddNewNotification(acceptedUser, message, link);
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
//...........................................
	private void HandshakeCountIncrease() {
		BadgeEntity badgeEntity;
					Query query = JPA.em().createQuery("from " + BadgeEntity.class.getName() + " where user_id=" + Security.getConnectedUserName().id);
					List<BadgeEntity> badgeEntityList = query.getResultList();
					badgeEntity=badgeEntityList.get(0);
					Long handshakeCount=badgeEntity.getHandshakeCount();
					handshakeCount++;
					
					if(handshakeCount==5){
					Date now = new Date();
		        	Timestamp cnow = new Timestamp(now.getTime());
		    		badgeEntity.setFivester((cnow.toString()));
					}
					
					if(handshakeCount==10){
						Date now = new Date();
			        	Timestamp cnow = new Timestamp(now.getTime());
			    		badgeEntity.setRater((cnow.toString()));
						}
						
					
			    		
		    		
					badgeEntity.setHandshakeCount(handshakeCount);
					badgeEntity.save();
					
		return;
	}

	public Object clone() {
        try {
        	return super.clone();
        } catch(CloneNotSupportedException e) {
            return null;
        }
    }
	
	public void setOccurrence(ServiceReoccurrenceData reoccurrenceData) {
		this.reoccurrenceData = reoccurrenceData;
		
		// create sessions.
		this.createSessions();
	}
	
	public static Service createSessionFromService(Service service, int index) {
		// create a new session (which is actually
		// a service), which is tied to the given
		// service.
		Service session = (Service) service.clone();
		session.title += " - " + (service.sessions.size() + 1);
		session.parent = service;
		session.startsAt = ServiceReoccurrenceData.addDaysToDate(service.startsAt, index * service.reoccurrenceData.repeatEveryNDay);
		session.endsAt = ServiceReoccurrenceData.addHoursToDate(session.startsAt, (int) session.duration);
		session.reoccurrenceData = null;
		
		// return the session.
		return session;
	}
	
	public static Service createSessionFromService(Service service, int dayIndex, int weekIndex, int realDayIndex) {
		// create a new session (which is actually
		// a service), which is tied to the given
		// service.
		Service session = (Service) service.clone();
		session.title += " - " + (service.sessions.size() + 1);
		session.parent = service;
		if (0 == weekIndex) {
			session.startsAt = ServiceReoccurrenceData.addDaysToDate(service.startsAt, (weekIndex * 7 * service.reoccurrenceData.repeatEveryNWeek) + dayIndex);
		} else {
			session.startsAt = ServiceReoccurrenceData.addDaysToDate(service.startsAt, (weekIndex * 7 * service.reoccurrenceData.repeatEveryNWeek) - realDayIndex + 1 + dayIndex);
		}
		session.endsAt = ServiceReoccurrenceData.addHoursToDate(session.startsAt, (int) session.duration);
		session.reoccurrenceData = null;
		
		// return the session.
		return session;
	}
}
